﻿using System;

namespace CSEngine
{
    public struct HexDigit
    {
        private byte digit_value;

        private HexDigit(byte value)
        {
            digit_value = value;
        }

        private HexDigit(char v)
        {
            digit_value = GetHexValue(v);
        }

        public static HexDigit Parse(string s)
        {
            char x;
            s = s.Trim();
            if (s.Length != 1)
                throw new ArgumentException();

            x = s[0];
            return FromChar(x);
        }

        public static bool TryParse(string s, out HexDigit hex)
        {
            try
            {
                hex = Parse(s);
            }
            catch (ArgumentException)
            {
                hex = new HexDigit();
                return false;
            }

            return true;
        }

        /// <summary>
        /// Get or set the value of this HexDigit
        /// </summary>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown in
        /// case of an attempt to set the value of this HexDigit to something
        /// outside the range 0-15 inclusive</exception>
        public byte Value
        {
            get { return digit_value; }
            set
            {
                try
                {
                    digit_value = FromByte(value).digit_value;
                }
                catch (ArgumentOutOfRangeException ex)
                {
                    throw new ArgumentOutOfRangeException("Invalid Hexadecimal Value: " + value.ToString(), ex);
                }
            }
        }

        public override string ToString()
        {
            string result = "ERROR";
            try
            {
                result = "" + (string)this;
            }
            catch (ArgumentOutOfRangeException)
            { }
            return result;
        }

        public static explicit operator HexDigit(byte b)
        {
            try
            {
                return FromByte(b);
            }
            catch (ArgumentOutOfRangeException ex)
            {
                throw new ArgumentOutOfRangeException("Invalid Hexadecimal Value: " + b.ToString(), ex);
            }
        }

        public static implicit operator string(HexDigit hex)
        {
            return "" + GetHexChar(hex.digit_value);
        }

        public static implicit operator byte(HexDigit hex)
        {
            return hex.digit_value;
        }

        /// <summary>
        /// Create a new instance of HexDigit
        /// </summary>
        /// <param name="c">Either a digit in the range '0'-'9', or a letter
        /// in the range 'a'-'f' or 'A'-'F' which corresponds to the hexadecimal
        /// value of the desired HexDigit instance</param>
        /// <returns>A new HexDigit with the appropriate value</returns>
        /// <exception cref="System.ArgumentException">Thrown when the character
        /// is not a valid hexadecimal digit</exception>
        public static HexDigit FromChar(char c)
        {
            try
            {
                return new HexDigit(GetHexValue(c));
            }
            catch (ArgumentException ex)
            {
                throw new ArgumentException("Invalid Hexadecimal Value: " + c.ToString(), ex);
            }
        }

        /// <summary>
        /// Create a new instance of HexDigit
        /// </summary>
        /// <param name="b">A value in the range 0-15 (inclusive)</param>
        /// <returns>A new HexDigit with the appropriate value</returns>
        /// <exception cref="System.ArgumentOutOfRangeException">Thrown when the value
        /// is outside the range 0-15 inclusive</exception>
        public static HexDigit FromByte(byte b)
        {
            try
            {
                return new HexDigit(GetHexChar(b));
            } catch (ArgumentOutOfRangeException ex)
            {
                throw new ArgumentOutOfRangeException("Invalid Hexadecimal Value: " + b.ToString(), ex);
            }
        }

        private static char GetHexChar(byte b)
        {
            switch (b)
            {
                case 1:
                    return '1';
                case 2:
                    return '2';
                case 3:
                    return '3';
                case 4:
                    return '4';
                case 5:
                    return '5';
                case 6:
                    return '6';
                case 7:
                    return '7';
                case 8:
                    return '8';
                case 9:
                    return '9';
                case 10:
                    return 'a';
                case 11:
                    return 'b';
                case 12:
                    return 'c';
                case 13:
                    return 'd';
                case 14:
                    return 'e';
                case 15:
                    return 'f';
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        private static byte GetHexValue(char c)
        {
            switch (c)
            {
                case '1':
                    return 1;
                case '2':
                    return 2;
                case '3':
                    return 3;
                case '4':
                    return 4;
                case '5':
                    return 5;
                case '6':
                    return 6;
                case '7':
                    return 7;
                case '8':
                    return 8;
                case '9':
                    return 9;
                case 'a':case 'A':
                    return 10;
                case 'b':case 'B':
                    return 11;
                case 'c':case 'C':
                    return 12;
                case 'd':case 'D':
                    return 13;
                case 'e':case 'E':
                    return 14;
                case 'f':case 'F':
                    return 15;
                default:
                    throw new ArgumentException();
            }
        }
    }
}
